#ifndef CRESOURCEMANAGER_H_
#define CRESOURCEMANAGER_H_

#include <map>
#include <vector>
#include "cresourceloader.h"
#include "../exception/cnullpointerexception.h"
#include "../exception/cindexoutofboundsexception.h"

/**
 * CResourceManager - general resource manager
 * @author Michal Rost
 * @date 09.04.2008
 */
template<typename T> class CResourceManager : public CObject {
public:

  /**
   * CResourceManager - creates empty resource manager
   * @param resLoaderPtr
   */
  CResourceManager(CResourceLoader<T>* resLoaderPtr = NULL) {
    this->defaultResId = RESOURCE_NONE;
    this->resLoaderPtr = resLoaderPtr;
  }
  //-------------------------------------------------------------------------

  /**
   * ~CResourceManager - destructor
   */
  virtual ~CResourceManager() {
    free();
  }
  //-------------------------------------------------------------------------

  /**
   * getDefaultResId - sets id of default resource
   * @param defaultResId default resource id
   */
  void setDefaultResId(int defaultResId) {
    this->defaultResId = defaultResId;
  }
  //-------------------------------------------------------------------------

  /**
   * getDefaultResId - returns id of default resource
   * @return default resource id
   */
  int getDefaultResId() const {
    return defaultResId;
  }
  //-------------------------------------------------------------------------

  /**
   * findResId - finds id of resource with given path
   * @param path path to resource
   * @return id
   */
  int findResId(string path) {
    map<string, int>::iterator i = idents.find(path);
    return i == idents.end() ? RESOURCE_NONE : i->second;
  }
  //-------------------------------------------------------------------------

  /**
   * addRes - adds new resource to resource manager
   * @param path path to resource in filesystem
   * @param res resource
   * @return id
   */
  int addRes(string path, T res) {
    int tmp(findResId(path));
    if (tmp == RESOURCE_NONE) {
      tmp = resources.size();
      resources.push_back(res);
      idents.insert(make_pair(path, tmp));
    }
    return tmp;
  }
  //-------------------------------------------------------------------------

  /**
   * getResPath - returns path to resource
   * @param id id of resource
   * @return path to resource in filesystem
   */
  string getResPath(int id) {
    map<string, int>::iterator i = idents.begin();
    for (; i != idents.end(); i++) {
      if (i->second == id) {
        return i->first;
      }
    }
    return "";
  }
  //-------------------------------------------------------------------------

  /**
   * loadRes - loads resource from filesystem and adds it to resource manager
   * @param path path to resource
   * @return id
   */
  int loadRes(string path) {

    // Test pointer
    if (resLoaderPtr == NULL) {
      throw CNullPointerException(getClassName(), "loadRes");
    }

    // Add new resource
    T tmp;
    resLoaderPtr->loadResource(path, tmp);
    return addRes(path, tmp);
  }
  //-------------------------------------------------------------------------

  /**
   * loadResources - bulk load resources
   * @param fileNames
   */
  void loadResources(vector<string> fileNames) {
    free();
    vector<string>::iterator i = fileNames.begin();
    for (; i < fileNames.end(); i++) {
      loadRes(*i);
    }
  }
  //-------------------------------------------------------------------------

  /**
   * reloadResources - reloads all resources in resource manager
   */
  void reloadResources() {

    // Test pointer
    if (resLoaderPtr == NULL) {
      throw CNullPointerException(getClassName(), "loadRes");
    }

    // Delete and reload resources
    map<string, int>::iterator i = idents.begin();
    for (; i != idents.end(); i++) {
      T tmp;
      resLoaderPtr->deleteResource(resources[i->second]);
      resLoaderPtr->loadResource(i->first, tmp);
      resources[i->second] = tmp;
    }
  }
  //-------------------------------------------------------------------------

  /**
   * getRes - returns resource with given id
   * @param id
   * @return resource
   */
  T getRes(int id) {
    if (id < 0 || id >= (int) resources.size()) {
      if (defaultResId != RESOURCE_NONE) {
        return getRes(defaultResId);
      }
      throw CIndexOutOfBoundsException(getClassName(), "getRes");
    }
    return resources[id];
  }
  //-------------------------------------------------------------------------

  /**
   * getNumOfResources - returns number of resources in resource manager
   * @return number of resources
   */
  TIndex getNumOfResources() const {
    return resources.size();
  }
  //-------------------------------------------------------------------------

  /**
   * getClassName - returns classname
   * @return classname
   */
  static string getClassName() {
    return "CResourceManager";
  }
  //-------------------------------------------------------------------------

  static const int RESOURCE_NONE = -1;
protected:
  int defaultResId;
  vector<T> resources;
  map<string, int> idents;
  CResourceLoader<T>* resLoaderPtr;

  /**
   * free - deletes all resources
   */
  void free() {
    for (unsigned int i = 0; i < resources.size(); i++) {
      resLoaderPtr->deleteResource(resources[i]);
    }
    resources.clear();
    idents.clear();
  }
  //-------------------------------------------------------------------------
};

#endif
